package discovery

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
)

// MemoryDiscovery implements in-memory service discovery
type MemoryDiscovery struct {
	services map[string]*ServiceInfo
	subs     map[string][]chan *ServiceEvent
	mutex    sync.RWMutex
	logger   *logrus.Logger
}

// NewMemoryDiscovery creates a new memory-based service discovery
func NewMemoryDiscovery(logger *logrus.Logger) *MemoryDiscovery {
	return &MemoryDiscovery{
		services: make(map[string]*ServiceInfo),
		subs:     make(map[string][]chan *ServiceEvent),
		logger:   logger,
	}
}

// Register registers a service instance
func (d *MemoryDiscovery) Register(ctx context.Context, service *ServiceInfo) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	if service.ID == "" {
		return fmt.Errorf("service ID is required")
	}

	service.LastSeen = time.Now()
	if service.StartTime.IsZero() {
		service.StartTime = time.Now()
	}
	if service.Status == "" {
		service.Status = StatusUp
	}

	d.services[service.ID] = service
	d.notifySubscribers(&ServiceEvent{
		Type:      EventTypeRegistered,
		Service:   service,
		Timestamp: time.Now(),
	})

	return nil
}

// Deregister removes a service instance
func (d *MemoryDiscovery) Deregister(ctx context.Context, serviceID string) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	if service, exists := d.services[serviceID]; exists {
		delete(d.services, serviceID)
		d.notifySubscribers(&ServiceEvent{
			Type:      EventTypeDeregistered,
			Service:   service,
			Timestamp: time.Now(),
		})
	}
	return nil
}

// GetService gets a service instance by ID
func (d *MemoryDiscovery) GetService(ctx context.Context, serviceID string) (*ServiceInfo, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	if service, exists := d.services[serviceID]; exists {
		return service, nil
	}
	return nil, fmt.Errorf("service not found: %s", serviceID)
}

// ListServices lists all service instances
func (d *MemoryDiscovery) ListServices(ctx context.Context) ([]*ServiceInfo, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	services := make([]*ServiceInfo, 0, len(d.services))
	for _, service := range d.services {
		services = append(services, service)
	}
	return services, nil
}

// ListServicesByName lists service instances by name
func (d *MemoryDiscovery) ListServicesByName(ctx context.Context, serviceName string) ([]*ServiceInfo, error) {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	var services []*ServiceInfo
	for _, service := range d.services {
		if service.Name == serviceName {
			services = append(services, service)
		}
	}
	return services, nil
}

// Watch watches for service changes
func (d *MemoryDiscovery) Watch(ctx context.Context) (<-chan *ServiceEvent, error) {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	ch := make(chan *ServiceEvent, 100)
	d.subs[""] = append(d.subs[""], ch)

	go func() {
		<-ctx.Done()
		d.mutex.Lock()
		defer d.mutex.Unlock()

		// Remove subscription when context is done
		subs := d.subs[""]
		for i, sub := range subs {
			if sub == ch {
				subs[i] = subs[len(subs)-1]
				subs = subs[:len(subs)-1]
				d.subs[""] = subs
				close(ch)
				break
			}
		}
	}()

	return ch, nil
}

// ReportStatus reports service status
func (d *MemoryDiscovery) ReportStatus(ctx context.Context, serviceID string, status ServiceStatus) error {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	service, exists := d.services[serviceID]
	if !exists {
		return fmt.Errorf("service not found: %s", serviceID)
	}

	if service.Status != status {
		service.Status = status
		service.LastSeen = time.Now()
		d.notifySubscribers(&ServiceEvent{
			Type:      EventTypeStatusChange,
			Service:   service,
			Timestamp: time.Now(),
		})
	}

	return nil
}

// notifySubscribers notifies all subscribers of a service event
func (d *MemoryDiscovery) notifySubscribers(event *ServiceEvent) {
	if subs, ok := d.subs[""]; ok {
		for _, ch := range subs {
			select {
			case ch <- event:
			default:
				// Skip if channel is full
			}
		}
	}
}

// StartHealthCheck starts the health check routine
func (d *MemoryDiscovery) StartHealthCheck(interval time.Duration) {
	ticker := time.NewTicker(interval)
	go func() {
		for range ticker.C {
			d.checkHealth()
		}
	}()
}

// checkHealth checks the health of all services
func (d *MemoryDiscovery) checkHealth() {
	d.mutex.Lock()
	defer d.mutex.Unlock()

	now := time.Now()
	for _, service := range d.services {
		if now.Sub(service.LastSeen) > 30*time.Second {
			if service.Status != StatusDown {
				service.Status = StatusDown
				d.notifySubscribers(&ServiceEvent{
					Type:      EventTypeStatusChange,
					Service:   service,
					Timestamp: now,
				})
			}
		}
	}
}
